function Player::reddenLimb(%player, %limb, %damage, %emphasis)
{
	//!rBloodPlayerShapeAllowed(%player.getDataBlock().shapeFile)
	if(!$Pref::RBloodMod::LimbReddening || !rBloodPlayerShapeAllowed(%player.getDataBlock().shapeFile))
		return;
		
	if(%emphasis $= "")
		%emphasis = 0;
		
	%emphasis += $Pref::RBloodMod::LimbReddeningFactor;
		
	// switch chest string to one specifically meant for redding and not dismemberment
	if(%limb == 1)
		%limb = 6;
		
	// find out how much reddening we will do based on damage
	%damageMax = %player.getDatablock().maxDamage * getWord(%player.scale, 2);
	%damage = mCeil((mClampF(%damage, 0, %damageMax) / %damageMax) * 5);
	
	//%emphasis = mClamp(mCeil((%damageMax - %damage) / 40), 1, 100);
	
	for(%i = 0; %i < getWordCount($RBloodLimbString[%limb]); %i++)
	{
		%limbName = getWord($RBloodLimbString[%limb], %i);
		
		// incrementally blend the color
		%newColor = %player.nodeColor[%limbName];
		for(%b = 0; %b < %damage; %b++)
		{
			//%newColor = calcAdditiveRGB(%newColor, "0.4 0 0 1");
			
			if(%emphasis == 0)
				%newColor = calcAdditiveRGB(%newColor, "0.4 0 0 1");
			else
				%newColor = calcAdditiveRGB_emphasis(%newColor, "0.4 0 0 1", %emphasis);
		}
		
		// set new color
		%player.setNodeColor(%limbName, %newColor);
		%player.RBlood_NodeOverrideColor[%limbName] = %newColor;
	}
}

function Player::causeBleeding(%player, %duration, %bloodOverDuration, %startTime, %spread)
{
	cancel(%player.rBloodBleedSchedule);
	
	%elapsed = (getSimTime() - %startTime);
	%stepTime = mClamp(%duration / %bloodOverDuration, 33, %duration);
	
	if(%spread > 0)
		%vector = vectorNormalize(vectorAdd("0 0 -1", ((getRandom() - 0.5) * %spread) SPC ((getRandom() - 0.5) * %spread) SPC ((getRandom() - 0.5) * %spread)));
	else
		%vector = "0 0 -1";
		
	doBloodSplatter(%vector, %player.getHackPosition(), 50);
	
	if(%elapsed > %duration)
		return;
	%player.rBloodBleedSchedule = %player.schedule(%stepTime, causeBleeding, %duration, %bloodOverDuration, %startTime, %spread);
}

function rbloodSplashNearbyPlayers(%position, %radius, %source, %intensity)
{
	%mask = $TypeMasks::PlayerObjectType;
	initContainerRadiusSearch(%position, %radius, %mask);
	while(%player = containerSearchNext())
	{
		//if(%player == %source)
		//	continue;
	
		%distance = mCeil(vectorDist(%player.getHackPosition(), %position));
		%iter = getRandom(3, 5);
		
		if(%intensity !$= "")
			%iter *= %intensity;
			
		if(%player == %source)
			%iter *= 0.75;
			
		%iter *= $Pref::RBloodMod::LimbReddeningSplash;
		
		for(%i = 0; %i < %iter; %i++)
		{
			%player.reddenLimb(getRandom(0, 5), 1, %distance);
		}
	}
}

// functions for spawning blood effects
function doBloodExplosion(%position, %scale)
{
	%bloodExplosionProjectile = new Projectile()
	{
		datablock = bloodBurstFinalExplosionProjectile;
		initialPosition = %position;
	};
	MissionCleanup.add(%bloodExplosionProjectile);
	%bloodExplosionProjectile.setScale(%scale SPC %scale SPC %scale);
	%bloodExplosionProjectile.explode();
}
function doBloodDismemberExplosion(%position, %scale)
{
	%bloodDismemberProjectile = new Projectile()
	{
		datablock = bloodDismemberProjectile;
		initialPosition = %position;
	};
	MissionCleanup.add(%bloodDismemberProjectile);
	%bloodDismemberProjectile.setScale(%scale SPC %scale SPC %scale);
	%bloodDismemberProjectile.explode();
}
function doGibLimbsExplosion(%position, %scale)
{
	for(%i = 0; %i < 3; %i++)
	{
		%datablock = $RBloodGib[%i];
		%bloodGibLimbsProjectile = new Projectile()
		{
			datablock = %datablock;
			initialPosition = %position;
		};
		MissionCleanup.add(%bloodGibLimbsProjectile);
		%bloodGibLimbsProjectile.setScale(%scale SPC %scale SPC %scale);
		%bloodGibLimbsProjectile.explode();
	}
}

// function for creating static-shape blood splatters that stick to surfaces
// these arguments are hot garbage lmao. Sorry in advance to anyone who wants to use this
function doBloodSplatter(%velocity, %position, %damage, %dataBlock)
{
	if($Pref::RBloodMod::DisableStatic)
		return;

	if(%dataBlock !$= "")
	{
		%lifetime = %dataBlock.lifetime;
		if(%lifetime < 50)
		{
			%range = ((getRandom() - 0.5) * 6);
		}
		else
		{
			%range = 10 * (%lifetime / 125);
		}
	}
	else
	{
		%range = getRandom(8, 14);
	}
	
	%spread = %range;
	
	%projectileVector = vectorScale(vectorNormalize(%velocity), %range);
	%projectileVector = vectorAdd(%projectileVector, ((getRandom() - 0.5) * %spread) SPC ((getRandom() - 0.5) * %spread) SPC ((getRandom() - 0.5) * %spread));
	%start = %position;
	%end = vectorAdd(%position, %projectileVector);
	
	%mask = $TypeMasks::FxBrickObjectType | $TypeMasks::TerrainObjectType;
	%raycast = containerRayCast(%start, %end, %mask);
	%hit = getWord(%raycast, 0);
	
	//drawLine(%start, %end, "1 1 0 0.5", 0.2);
	
	if(isObject(%hit))
	{
		%end = getWords(%raycast, 1, 3);
		%normal = getWords(%raycast, 4, 6);
		%rotation = Normal2Rotation(%normal);
		%doStatic = true;
	}
	else
	{
		// hit nothing, simulate blood going to the floor
		%start = %end;
		%end = setWord(%start, 2, getWord(%start, 2) - 50);
		%raycast = containerRayCast(%start, %end, %mask);
		%hit = getWord(%raycast, 0);
		
		if(isObject(%hit))
		{
			%end = getWords(%raycast, 1, 3);
			%normal = getWords(%raycast, 4, 6);
			%rotation = Normal2Rotation(%normal);
			%doStatic = true;
		}
	}
	
	if(%doStatic)
	{
		%bloodPuddle = new StaticShape()
		{
			datablock = $RBloodPuddleStatic[getRandom(0, 1)];
			position = %end;
			rotation = %rotation;
			normal = %normal;
		};
		MissionCleanup.add(%bloodPuddle);
		%bloodPuddle.setNodeColor("ALL", $Pref::RBloodMod::BloodColor);
		
		%lifetime = $Pref::RBloodMod::BloodFadeTime;
		%bloodPuddle.schedule(%lifetime, delete);
		%bloodPuddle.bloodFadeLoop(%lifetime, 50, $Pref::RBloodMod::BloodColor);
		
		%scale = (mCeil(%damage / 64) + (2 * getRandom())) / 2;
		%bloodPuddle.setScale(%scale SPC %scale SPC %scale);
		
		if(!$Pref::RBloodMod::DisablePuddleTrigger)
			createBloodPuddleTrigger(%end);
		
		%downwardAngle = mRadtoDeg(mACos(vectorDot(%normal, "0 0 -1")));
		if(%downwardAngle < 80)
		{
			%bloodPuddle.doDripRBlood();
		}
	}
}

function createBloodPuddleTrigger(%position)
{
	%trigger = new Trigger()
	{ 
		datablock = rbloodPuddleTriggerData;
		position = %position;
		polyhedron = "-0.5 -0.5 -0.5 1 0 0 0 1 0 0 0 1";
		creationTime = getSimTime();
	};
	%trigger.schedule($Pref::RBloodMod::BloodFadeTime * 0.75, delete);
	%trigger.setScale(0.5 SPC 0.5 SPC 0.2);
	return;
}

function rbloodPuddleTriggerData::onEnterTrigger(%data, %trigger, %player)
{
	if(!rBloodPlayerShapeAllowed(%player.getDataBlock().shapeFile))
		return;
		
	%timeDif = getSimTime() - %trigger.creationTime;
	if(%timeDif < 1000)
		return;

	if(!%trigger.triggered)
	{
		%trigger.triggered = true;
		%player.reddenLimb(getRandom(4, 5), 1, 5);
	}
	return;
}

// function for fading a static shape out to invisibility over a certain period of time and increments
function StaticShape::bloodFadeLoop(%shape, %lifetime, %increments, %color)
{
	cancel(%shape.fadeLoopSchedule);
	%loopTime = %lifetime / %increments;
	
	%RGB = getWords(%color, 0, 2);
	%alpha = getWord(%color, 3);
	
	%alpha = %alpha - (getWord($Pref::RBloodMod::BloodColor, 3) / %increments);
	
	%newColor = %RGB SPC %alpha;
	%shape.setNodeColor("ALL", %newColor);

	%shape.fadeAlpha = %alpha;
	%shape.fadeLoopSchedule = %shape.schedule(%loopTime, bloodFadeLoop, %lifetime, %increments, %newColor);
}

function StaticShape::doDripRBlood(%shape)
{
	cancel(%shape.rbloodDripLoop);

	%scale = mClampF(%shape.fadeAlpha, 0, 0.8);
	
	%vector = vectorNormalize(%shape.normal);
	%position = %shape.getPosition();
	
	%end = vectorAdd(%position, vectorScale(%vector, 0.25));
	//drawLine(%position, %end, "1 0 0 0.2", 0.2);
	
	%bloodDripProjectile = new Projectile()
	{
		datablock = rbloodDripProjectile;
		initialPosition = %end;
		initialVelocity = "0 0 -1";
		fadeAlpha = %shape.fadeAlpha;
	};
	MissionCleanup.add(%bloodDripProjectile);
	%bloodDripProjectile.setScale(%scale SPC %scale SPC %scale);
	
	%shape.rbloodDripLoop = %shape.schedule(getRandom(33, 2000), doDripRBlood);
}

function Player::RBloodSimulate(%obj, %position, %velocity, %projectileData, %projectileScale, %dismemberOnKill, %damage) {
	%data = %obj.getDatablock();
	//%damage = (%projectileData.directDamage * %projectileScale);
	%damage = %damage * 100 / %obj.getDatablock().maxDamage; // scale blood effects by HP %
	
	if(!rBloodPlayerShapeAllowed(%data.shapeFile))
		return;
	
	%obj.lastRBloodSimTime = getSimTime();
	
	// Some information about who we just shot
	%maxHP = %data.maxDamage;
	%currentHP = %maxHealth * (1 - %obj.getDamagePercent());
	%scale = getWord(%obj.getScale(), 2);
	
	%limb = %obj.rgetDamageLocation(%position);
	
	// Damage base blood effect
	%effectPosition = %position;
	%bloodAmount = mCeil(%damage / 16);
	%bloodAmountMax = 250;
	if(%bloodAmount>%bloodAmountMax) %bloodAmount = %bloodAmountMax;
	for(%i = 0; %i < %bloodAmount; %i++)
	{
		doBloodExplosion(%effectPosition, %scale);
	}
	schedule(0, 0, rbloodSplashNearbyPlayers, %effectPosition, 4, %obj, 0.6);
	serverPlay3D($RBlood::smallHit[getRandom(0, 3)], %effectPosition);
	
	// Blood splatters (static)
	for(%i = 0; %i < getRandom(2, 6); %i++)
	{
		doBloodSplatter(%velocity, %position, %damage, %projectileData);
	}
	
	// Hencefourth blood splattering caused by bleeding
	if(!$Pref::RBloodMod::DisableBleed)
	{
		%bloodTime = mCeil((%damage * 100) * $Pref::RBloodMod::BloodTimeScale);
		%bleedAmount = mCeil((%damage / 2) * $Pref::RBloodMod::BloodAmountScale);
		%obj.causeBleeding(%bloodTime, %bleedAmount, getSimTime());
	}
	
	// Node reddining on damage
	if($Pref::RBloodMod::LimbReddening)
	{
		%obj.reddenLimb(%limb, %damage);
	}
	
	// Dismemberment
	// Occurs only with high-powered weapons, like shotguns and 1-shot weapons
	// Dismemberment can only occur after death, so we store the limbs that are
	// marked for dismemberment, and then check the values at death for a valid
	// dismemberment condition.
	%obj.limbShotgunStrike = (getSimTime() - %obj.lastLimbHitTime[%limb] < 5);	// if the limb was hit more than once within 5ms, we can assume we were shot by a shotgun
	%obj.lastLimbHitTime[%limb] = getSimTime();
	%obj.lastLimbDamage[%limb] = %damage;
	%obj.lastLimbDamagePosition[%limb] = %effectPosition;
	
	if(%damage > %maxHP || %obj.limbShotgunStrike || %damage >= $Pref::RBloodMod::DismemberDamage || %dismemberOnKill)
	{
		%obj.markLimbForDismember[%limb] = true;
		for(%i = 0; %i < getRandom(2, 6); %i++)
		{
			doBloodSplatter(%velocity, %position, %damage, %projectileData);
		}
		schedule(0, 0, rbloodSplashNearbyPlayers, %effectPosition, 6, %obj);
	}
}


package RBloodPackage
{
	function Armor::Damage(%data, %obj, %sourceObject, %position, %damage, %damageType)
	{
		// Armor::Damage hook will provide functionality for weapons that do not use projectiles.
		// Due to the nature of Armor::Damage, this process is very tedious and ambiguous, and uses
		// a lot of hacky assumptive solutions.
		
		// First, determine whether or not RBlood was simulated already. if it has, there's no need to continue.
		%doRBloodSim = (getSimTime() - %obj.lastRBloodSimTime > 5);
		if(!%doRBloodSim)
			return Parent::Damage(%data, %obj, %sourceObject, %position, %damage, %damageType);
		
		if(%damage < $Pref::RBloodMod::MinimumBloodThreshold)
			return Parent::Damage(%data, %obj, %sourceObject, %position, %damage, %damageType);
			
		// We need to exclude certain damages that wouldn't make a lot of sense. You don't bleed in lava nor dor your limbs
		// explode when you commit suicide.
		if(%damageType == $DamageType::Lava || %damageType == $DamageType::Suicide)
			return Parent::Damage(%data, %obj, %sourceObject, %position, %damage, %damageType);
		
		%rbloodPosition = %position;
		%scale = getWord(%obj.scale, 2);
		%hs = 1;
		%vs = 2;
		%randomString = ((getRandom() - 0.5) * %hs * %scale) SPC ((getRandom() - 0.5) * %hs * %scale) SPC ((getRandom() - 0.5) * %vs * %scale);
				
		// Add-on makers don't always put in accurate information for %position. Sometimes this is done on purpose. Either way, we need to account for it.
		if(%rbloodPosition $= "" || %rbloodPosition $= "0 0 0" || vectorDist(%rbloodPosition, %obj.getHackPosition()) > (1.5 * %scale))
		{
			// bogus position. try to comp
			// Will get the center of the player and then randomly offset
			%rbloodPosition = %obj.getHackPosition();
			%rbloodPosition = vectorAdd(%rbloodPosition, %randomString);
		}
		
		// No projectile = no vector. We have no idea where the damage came from, so just bleed in the general downwards direction.
		%vector = vectorNormalize(vectorAdd("0 0 0", %randomString));
		
		// Dismembering.
		%dismember = (%damage >= $Pref::RBloodMod::DismemberDamage);
		
		%damage_old = %obj.getDamageLevel();
		Parent::Damage(%data, %obj, %sourceObject, %position, %damage, %damageType);
		%damage_new = %obj.getDamageLevel();
		
		if(%damage_new > %damage_old) {
			if(%damage_new < %data.maxDamage) %damage = %damage_new - %damage_old;
			%obj.schedule(0, RBloodSimulateArmor, %rbloodPosition, %vector, swordProjectile, 1, %dismember, %damage);
		}
	}
	
	function Player::RBloodSimulateArmor(%obj, %pos, %vec, %proj, %scl, %dis, %dmg) {
		if(getSimTime() - %obj.lastRBloodSimTime < 5) return;
		%obj.RBloodSimulate(%pos, %vec, %proj, %scl, %dis, %dmg);
	}
	
	function ProjectileData::Damage(%this, %projectile, %obj, %fade, %position, %normal)
	{
		%damage = (%this.directDamage * %projectile.scale);
		
		// avoid blood on damageless weapons like pushbrooms
		if(%damage < $Pref::RBloodMod::MinimumBloodThreshold)
			return Parent::Damage(%this, %projectile, %obj, %fade, %position, %normal);
		
		// functionality for developers who want to disable gore on their weapons
		if(%this.disableGore)
			return Parent::Damage(%this, %projectile, %obj, %fade, %position, %normal);
		
		if(%obj.getClassName()!$="Player" && %obj.getClassName()!$="AIPlayer")
			return Parent::Damage(%this, %projectile, %obj, %fade, %position, %normal);
		
		%damage_old = %obj.getDamageLevel();
		Parent::Damage(%this, %projectile, %obj, %fade, %position, %normal);
		%damage_new = %obj.getDamageLevel();
		
		if(%damage_new > %damage_old) {
			if(%damage_new < %data.maxDamage) %damage = %damage_new - %damage_old;
			%obj.RBloodSimulate(%position, %projectile.getVelocity(), %this, %projectile.scale, %this.dismemberOnKill, %damage);
		}
	}
	
	function Armor::onDisabled(%this, %obj, %state)
	{
		%position = %obj.getHackPosition();
		
		// check each limb for a dismemberment flag
		for(%limb = 0; %limb <= 5; %limb++)
		{
			%time = (getSimTime() - %obj.lastLimbHitTime[%limb]) < $Pref::RBloodMod::DismemberTime;
			%dism = %obj.markLimbForDismember[%limb];
			%dama = %obj.lastLimbDamage[%limb];
			
			%effectPosition = %obj.lastLimbDamagePosition[%limb];
			
			if(%time && %dism)
			{
				for(%i = 0; %i < getWordCount($RBloodLimbString[%limb]); %i++)
				{
					%obj.hideNode(getWord($RBloodLimbString[%limb], %i));
				}
				
				doBloodDismemberExplosion(%position, 1);
				
				if(!%obj.markForGibExplosion && isObject($RBlood_Stump[%limb]) && $Pref::RBloodMod::UseLimbStubs)
					%obj.mountimage($RBlood_Stump[%limb], 2);
				
				serverPlay3D($RBlood::HeavyHit[getRandom(0, 3)], %effectPosition);
			}
		}
		
		// were we in the middle of an explosion when we were killed? assume we were killed in an explosion and gib us
		if(%obj.markForGibExplosion)
		{
			%time = (getSimTime() - %obj.markForGibExplosionTime < 5);
			if(%time)
			{
				// hide everything and mount a ribcage to our chest, giving the illusion that it's part of the player model
				%obj.hideNode("ALL");
				%obj.setNodeColor("ALL", "0.4 0 0 1");
				%obj.mountimage(RBloodRibcageImage, 2);
				%obj.unMountImage(0);
				
				for(%i = 0; %i < getRandom(2, 3); %i++)
				{
					doBloodDismemberExplosion(%position, 1.5);
					doBloodExplosion(%position, 2);
				}
				
				%splatterAMT = getRandom(8, 16);
				%splatterAMT *= $Pref::RBloodMod::StaticBloodAmt;
				for(%i = 0; %i < %splatterAMT; %i++)
				{
					%vector = (getRandom() - 0.5) * 2 SPC (getRandom() - 0.5) * 2 SPC (getRandom() - 0.5) * 2;
					doBloodSplatter(%vector, %position, 100);
				}
				
				schedule(0, 0, rbloodSplashNearbyPlayers, %position, 10, %obj, 5);
				doGibLimbsExplosion(%position, getWord(%obj.scale, 2));
			}
		}
		Parent::onDisabled(%this, %obj, %state);
	}
	
	function ProjectileData::radiusDamage(%this, %obj, %col, %distanceFactor, %pos, %damageAmt)
	{
		%damage = %damageAmt * mClampF(%distanceFactor, 0, 1);
	
		if(!rBloodPlayerShapeAllowed(%col.getdataBlock().shapeFile) || %damage < $Pref::RBloodMod::MinimumBloodThreshold || %this.disableGore)
			return Parent::radiusDamage(%this, %obj, %col, %distanceFactor, %pos, %damageAmt);
			
		%hackPosition = %col.getHackPosition();
		%bloodAmount = mCeil(%damage / 24);
		for(%i = 0; %i < %bloodAmount; %i++)
		{
			doBloodExplosion(%hackPosition, 1);
		}
		schedule(0, 0, rbloodSplashNearbyPlayers, %hackPosition, 4, %col);
		
		if(!$Pref::RBloodMod::DisableBleed)
		{
			%bloodTime = mCeil((%damage * 50) * $Pref::RBloodMod::BloodTimeScale);
			%bleedAmount = mCeil((%damage / 2) * $Pref::RBloodMod::BloodAmountScale);
			%col.causeBleeding(%bloodTime, %bleedAmount, getSimTime());
		}
		
		if(%damageAmt < $Pref::RBloodMod::MinimumGibDamage)
			return Parent::radiusDamage(%this, %obj, %col, %distanceFactor, %pos, %damageAmt);
		
		%hackDistance = vectorDist(%pos, %hackPosition);
	
		if((%hackDistance / getWord(%col.scale, 2)) < $Pref::RBloodMod::DistanceToGib)
		{
			%col.markForGibExplosion = true;
			%col.markForGibExplosionTime = getSimTime();
		}
		
		Parent::radiusDamage(%this, %obj, %col, %distanceFactor, %pos, %damageAmt);
	}
	
	function ShapeBase::setNodeColor(%obj, %node, %color)
	{
		Parent::setNodeColor(%obj, %node, %color);
		
		// silly work around for not having a legitimate way to get node colors
		//rBloodPlayerShapeAllowed(%shape)
		%shape = %obj.getDatablock().shapeFile;
		if(rBloodPlayerShapeAllowed(%shape)) 
			%obj.nodeColor[%node] = %color;
	}
};
activatePackage(RBloodPackage);
